library(tidyverse)         # for graphing and data cleaning
library(tidymodels)        # for modeling
library(themis)  
library(doParallel)        # for parallel processing
library(stacks)            # for stacking models
library(naniar)            # for examining missing values (NAs)
library(lubridate)         # for date manipulation
library(moderndive)        # for King County housing data
library(vip)               # for variable importance plots
library(patchwork)         # for combining plots nicely
library(ranger)
library(xgboost)
theme_set(theme_minimal()) # Lisa's favorite theme
data("lending_club")
# Data dictionary (as close as I could find): https://www.kaggle.com/wordsforthewise/lending-club/discussion/170691

Modeling

We’ll be using the lending_club dataset from the modeldata library, which is part of tidymodels. The data dictionary they reference doesn’t seem to exist anymore, but it seems the one on this kaggle discussion is pretty close. It might also help to read a bit about Lending Club before starting in on the exercises.

The outcome we are interested in predicting is Class. And according to the dataset’s help page, its values are “either ‘good’ (meaning that the loan was fully paid back or currently on-time) or ‘bad’ (charged off, defaulted, or 21-120 days late)”.

Tasks:

  1. Explore the data, concentrating on examining distributions of variables and examining missing values.

This dataset has 23 variables. We are going to look at the distribution of quantitative and categorical variables.

lending_club %>% 
  select(where(is.numeric)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_histogram(bins = 30) +
  facet_wrap(vars(variable), 
             scales = "free")

According to the graphs above, we can see that there are many variables that are right skewed such as annual_inc, inq_last_12m, num_il_tl, open_il_24m, open_il_6m, total_bal_il, and total_il_high_credit_li.

lending_club %>% 
  select(where(is.factor)) %>% 
  pivot_longer(cols = everything(),
               names_to = "variable", 
               values_to = "value") %>% 
  ggplot(aes(x = value)) +
  geom_bar() +
  facet_wrap(vars(variable), 
             scales = "free", 
             nrow = 2)

We can see that there are 6 categorical variables in the dataset. These variables are all well distributed. If we look at the ‘Class’ variable, most of the data points are in good category.

  1. Split the data into training and test, putting 75% in the training data. Stratify by Class (add strata =Classto theinitial_split()` function).
set.seed(494) # for reproducibility

# remove the #'s once you've defined these - this is so we all have the same name
lending_split <- initial_split(lending_club, strata = 'Class',
                             prop = .75)

lending_training <- training(lending_split)
lending_test <- testing(lending_split)
  1. Set up the recipe and the pre-processing steps to build a lasso model. Some steps you should take:
  • Use step_upsample() from the themis library to upsample the “bad” category so that it is 50% of the “good” category. Do this by setting over_ratio = .5.
  • Use step_downsample() from the themis library to downsample the “good” category so the bads and goods are even - set under_ratio = 1. Make sure to do this step AFTER step_upsample().
  • Make all integer variables numeric (I’d highly recommend using step_mutate_at() and using the all_numeric() helper or this will be a lot of code). This step might seem really weird right now, but we’ll want to do this for the model interpretation we’ll do in a later assignment.
  • Think about grouping factor variables with many levels.
  • Make categorical variables dummy variables (make sure NOT to do this to the outcome variable).
  • Normalize quantitative variables.

Once you have that, use prep(), juice(), and count() to count the number of observations in each class. They should be equal. This dataset will be used in building the model, but the data without up and down sampling will be used in evaluation.

set.seed(456)

lasso_recipe <- recipe(Class ~ ., data = lending_training) %>% 
  step_upsample(Class, over_ratio = 0.5) %>% 
  step_downsample(Class, under_ratio = 1) %>% 
  step_mutate_at(all_numeric(), fn = ~as.numeric(.)) %>% 
  step_mutate(sub_grade = as.character(sub_grade), 
              grade = as.factor(str_sub(sub_grade,1,1)))%>% 
  step_rm(sub_grade) %>% 
  step_dummy(all_nominal_predictors()) %>% 
  step_normalize(all_numeric_predictors())
lasso_recipe %>% 
  prep(lending_training) %>%
  juice() 
  1. Set up the lasso model and workflow. We will tune the penalty parameter.
lasso_mod <-  
  logistic_reg(mixture = 1) %>% 
  set_engine("glmnet") %>% 
  set_args(penalty = tune()) %>% 
  set_mode("classification")

lasso_wf <- 
  workflow() %>% 
  add_recipe(lasso_recipe) %>% 
  add_model(lasso_mod)

lasso_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
## 
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 7 Recipe Steps
## 
## • step_upsample()
## • step_downsample()
## • step_mutate_at()
## • step_mutate()
## • step_rm()
## • step_dummy()
## • step_normalize()
## 
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
## 
## Main Arguments:
##   penalty = tune()
##   mixture = 1
## 
## Computational engine: glmnet
  1. Set up the model tuning for the penalty parameter. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Find the accuracy and area under the roc curve for the model with the best tuning parameter. Use 5-fold cv.
set.seed(494) 
lending_cv <- vfold_cv(lending_training, v = 5)

penalty_grid <- grid_regular(penalty(),
                             levels = 10)

ctrl_grid <- control_stack_grid()

lasso_tune <- lasso_wf %>% 
  tune_grid(
    resamples = lending_cv,
    grid = penalty_grid, 
    control = ctrl_grid)

Here we find the accuracy of the model:

lasso_acc <- lasso_tune %>% 
  show_best("accuracy") %>% 
  filter(penalty == (lasso_tune %>%  select_best("accuracy"))$penalty)
lasso_acc

Here is the ROC curve metric:

lasso_roc <- lasso_tune %>% 
  show_best("roc_auc") %>% 
  filter(penalty == (lasso_tune %>%  select_best("roc_auc"))$penalty)
lasso_roc

Using accuracy metric, we get 0.7229 for our mean with Preprocessor1_Model09, and using ROC curve metric, we get slightly higher for our mean which is 0.7352 for Preprocessor1_Model09 too.

  1. Set up the recipe and the pre-processing steps to build a random forest model. You shouldn’t have to do as many steps. The only steps you should need to do are making all integers numeric and the up and down sampling.
set.seed(456)
rf_recipe <- recipe(Class ~ ., data = lending_training) %>% 
  step_upsample(Class, over_ratio = 0.5) %>% 
  step_downsample(Class, under_ratio = 1) %>% 
  step_mutate_at(all_numeric(), fn = ~as.numeric(.)) 
  1. Set up the random forest model and workflow. We will tune the mtry and min_n parameters and set the number of trees, trees, to 100 (otherwise the next steps take too long).
rf_model <- rand_forest(
              mtry = tune(), 
              min_n = tune(), 
              trees = 100) %>% 
  set_mode("classification") %>%
  set_engine("ranger")

rf_workflow <- workflow() %>%
  add_recipe(rf_recipe) %>%
  add_model(rf_model)
  1. Set up the model tuning for both the mtry and min_n parameters. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack. Use only 3 levels in the grid. For the mtry parameter, you need to put finalize(mtry(), lending_training %>% select(-Class)) in as an argument instead of just mtry(), where lending_training is the name of your training data. This is because the mtry() grid will otherwise have unknowns in it. This part can take a while to run.
boost_mtry_and_min_n <- grid_regular(finalize(mtry(), lending_training %>% select(-Class)),
                           min_n(),
                           levels = 3)
boost_mtry_and_min_n
ctrl_grid <- control_stack_grid()
rf_tune <- 
  rf_workflow %>% 
  tune_grid(
  resamples = lending_cv ,
  grid = boost_mtry_and_min_n,
  control = ctrl_grid
)
  1. Find the best tuning parameters. What are the accuracy and area under the ROC curve for the model with those tuning parameters?

Here is the result using accuracy metric:

rf_acc <- rf_tune %>% 
  show_best("accuracy") %>% 
  filter(mtry == (rf_tune %>%  select_best("accuracy"))$mtry, 
       min_n == (rf_tune %>%  select_best("accuracy"))$min_n)
rf_acc

Here is the result using ROC curve metric:

rf_roc <- rf_tune %>% 
  show_best("roc_auc") %>% 
  filter(mtry == (rf_tune %>%  select_best("roc_auc"))$mtry, 
       min_n == (rf_tune %>%  select_best("roc_auc"))$min_n)

rf_roc

Based on the two tables above, we can see that with accuracy metric, the best accuracy is 92.84%. However, the ROC curve metrics shows a slightly lower accuracy which is only 73.5%.

  1. Next, we will fit a boosted tree using xgboost. We will only tune the learn_rate parameter. I have specified the model, recipe, and workflow below already (uncomment the code - you can this by highlighting it and then in the code tab at the top, choose comment/uncomment lines). You need to set up a grid of ten values for the tuning parameter and tune the model. Be sure to add the control_stack_grid() for the control argument so we can use these results later when we stack.
xgboost_spec <-
  boost_tree(
    trees = 1000,
    min_n = 5,
    tree_depth = 2,
    learn_rate = tune(),
    loss_reduction = 10^-5,
    sample_size = 1) %>%
  set_mode("classification") %>%
  set_engine("xgboost")

xgboost_recipe <- recipe(formula = Class ~ ., data = lending_training) %>%
  step_upsample(Class, over_ratio = .5) %>%
  step_downsample(Class, under_ratio = 1) %>%
  step_mutate_at(all_numeric(),
                 fn = ~as.numeric(.)) %>%
  step_novel(all_nominal_predictors()) %>%
  step_dummy(all_nominal_predictors(), one_hot = TRUE) %>%
  step_zv(all_predictors())

xgboost_workflow <-
  workflow() %>%
  add_recipe(xgboost_recipe) %>%
  add_model(xgboost_spec)

set.seed(494)
registerDoParallel() 

boost_learn_rate<- grid_regular(learn_rate(),
                           levels = 3)

boost_tune <- xgboost_workflow %>% 
  tune_grid(
  resamples = lending_cv ,
  grid = boost_learn_rate,
  control = ctrl_grid
)
  1. Find the best tuning parameters. What are the accuracy and area under the ROC curve for the model with those tuning parameters?

Best tuning parameter for accuracy:

best_param_xgboost_acc <- boost_tune %>% 
  select_best(metric = "accuracy") %>% 
  filter(learn_rate == (boost_tune %>%  select_best("accuracy"))$learn_rate)
best_param_xgboost_acc

Best turning parameter for ROC curve:

best_param_xgboost_roc_curve <- boost_tune %>% 
  select_best(metric = "roc_auc") %>% 
  filter(learn_rate == (boost_tune %>%  select_best("roc_auc"))$learn_rate)
best_param_xgboost_roc_curve

We can see that the learn_rate for accuracy is 0.1, while the learn_rate for the ROC curve is 3.162278e-06.

  1. Create a model stack with the candidate models from the previous parts of the exercise and use the blend_predictions() function to find the coefficients of the stacked model. Create a plot examining the performance metrics for the different penalty parameters to assure you have captured the best one. If not, adjust the penalty. (HINT: use the autoplot() function). Which models are contributing most?
lending_stack <- 
  stacks() %>% 
  add_candidates(lasso_tune) %>% 
  add_candidates(rf_tune) %>% 
  add_candidates(boost_tune)
as_tibble(lending_stack)
lending_blend <- lending_stack %>% 
  blend_predictions()

lending_blend
## # A tibble: 6 × 3
##   member                     type          weight
##   <chr>                      <chr>          <dbl>
## 1 .pred_good_boost_tune_1_2  boost_tree   250.   
## 2 .pred_good_lasso_tune_1_09 logistic_reg   2.52 
## 3 .pred_good_rf_tune_1_4     rand_forest    1.79 
## 4 .pred_good_lasso_tune_1_08 logistic_reg   0.724
## 5 .pred_good_rf_tune_1_5     rand_forest    0.305
## 6 .pred_good_boost_tune_1_3  boost_tree     0.232
autoplot(lending_blend, type = "members")

autoplot(lending_blend, type = "weights")

From the graph above, we can see that boost_tree contributes the most to the model with stacking coefficient = 250.

  1. Fit the final stacked model using fit_members(). Apply the model to the training data. Compute the accuracy, construct a confusion matrix, and create a density plot with .pred_good on the x-axis (the probability of a response of “good”), filled by Class. Comment on what you see.
lending_final_stack <- lending_blend %>% 
  fit_members()
lending_final_good_bad <- lending_final_stack %>% 
  predict(new_data = lending_training)
lending_final_prediction <- lending_final_stack %>% 
  predict(new_data = lending_training, type = "prob") %>% 
  bind_cols(lending_training) %>% 
  bind_cols(lending_final_good_bad)

lending_final_prediction
lending_final_prediction %>%
  conf_mat(truth = Class, estimate = .pred_class)
##           Truth
## Prediction  bad good
##       bad     0    0
##       good  383 7009

True positive rate: 7009/(7009+383) = 0.948 True negative rate: 0/(0+0) = 0/0

The accuracy will be: 7009/(7009+383) = 0.948

We are going to create a density of plot of pred_good categorized by class.

lending_final_prediction %>%
  ggplot(aes(x = .pred_good, fill = Class)) + geom_density(alpha = 0.5, color = NA)

We can see that the majority of Class is predicted as good. There is only a small area that are overlapped so the accuracy will be fairly high. And the blened model doesn’t predict any bad class as the number of bad class is fairly small compared to good class.

  1. In the previous problem, you saw that although the accuracy was quite high, the true negative rate (aka sensitivity) was terrible. It’s common to see this when one of the classes has low representation. What we want to do now is investigate what happens in each of our models. Below I’ve provided code to investigate the lasso model (where lasso_tune is the name of my tuning step). Do similar things for the random forest and xgboost models. If you’d like to have a better true negative rate, which models would you choose and how would you go about doing this in a less manual way (you don’t need to write code to do it - just describe it in words). Be sure to remove the eval=FALSE when you are finished.
lasso_tune %>% 
  collect_predictions() %>% 
  group_by(id, penalty) %>% 
  summarize(accuracy = sum((Class == .pred_class))/n(),
            true_neg_rate = sum(Class == "bad" & .pred_class == "bad")/sum(Class == "bad"),
            true_pos_rate = sum(Class == "good" & .pred_class == "good")/sum(Class == "good")) %>% 
  group_by(penalty) %>% 
  summarize(across(accuracy:true_pos_rate, mean))
### Random forest
rf_tune %>% 
  collect_predictions() %>% 
  group_by(id, mtry, min_n) %>% 
  summarize(accuracy = sum((Class == .pred_class))/n(),
            true_neg_rate = sum(Class == "bad" & .pred_class == "bad")/sum(Class == "bad"),
            true_pos_rate = sum(Class == "good" & .pred_class == "good")/sum(Class == "good")) %>% 
  group_by(mtry,min_n) %>% 
  summarize(across(accuracy:true_pos_rate, mean))
boost_tune %>% 
  collect_predictions() %>% 
  group_by(id, learn_rate) %>% 
  summarize(accuracy = sum((Class == .pred_class))/n(),
            true_neg_rate = sum(Class == "bad" & .pred_class == "bad")/sum(Class == "bad"),
            true_pos_rate = sum(Class == "good" & .pred_class == "good")/sum(Class == "good")) %>% 
  group_by(learn_rate) %>% 
  summarize(across(accuracy:true_pos_rate, mean))

Looking at all the result above, we can get the highest true negative rate (1) if we choose the xgboost model with 1.000000e-10 learn_rate, but our true positive rate will be 0 which is not what we want.

If we want a model that has both good true positive and true negative rate, I would choose the xgboost model with 3.162278e-06 learn_rate. With this model, our true negative rate is 0.6955410 and true positive rate is 0.648753.

In a less manual way, I would try to optimize the true negative rate. However, there is a trade off between the negative rate and positive rate. Higher negative rate can impact the true positive rate and overall accuracy.

Shiny App

For this week, there is no code to turn in for this part. You are just going to need to think about the steps to take.

If you are new to Shiny apps or it’s been awhile since you’ve made one, visit the Shiny links on our course Resource page. I would recommend starting with my resource because it will be the most basic.

Everyone should watch the Theming Shiny talk by Carson Sievert so you can make your app look amazing.

Tasks:

In the future, you are going to create an app that allows a user to explore how the predicted probability of a loan being paid back (or maybe just the predicted class - either “good” or “bad”) changes depending on the values of the predictor variables.

For this week, I want you to answer the following questions:

  1. How can you save a model you built to use it later (like in the shiny app you’ll create)?

To save the model I built to use it later, I would use the syntax: save(model, file=“…”).

  1. For shiny apps that get published (like yours will), it’s very important to have ALL the libraries that are used within the app loaded. If we were going to use the stacked model, which libraries do you think we’d need to load in our app?

We have to load all the libraries that we would need in order to build our models such as lasso, random_forest, or xgboost. Then, we would need to load library(stacks). Finally, if we want to visualize our models, we need to load ggplot2, tidyverse, and tidymodels.

  1. You’ll want the user to be able to choose values for each variable in the model. How will you come up with the values they can choose for quantitative and categorical data? Give one example for each, either using code or in words.

For quantitative data, I would let them choose the input within the 50% data in the middle using sliderInput(). And for the categorical data, I would give them some options by using selectInput().

  1. You will need to populate each variable with an initial value. Which value will you choose? Is there a nice way to do this programatically (ie. with code)?

For the initial value, we would choose the mean of the quantitative varibles, and for the categorical variables, I would choose the most dominant one in the dataset.

Coded Bias

We will be watching some of the Coded Bias film together on Thursday. It is streaming on Netflix. Write a short reflection. If you want some prompts, reflect on: What part of the film impacted you the most? Was there a part that surprised you and why? What emotions did you experience while watching?

The part where impacted me the most and I still remember now is when the facial recognition algorithm can only detect her face when she covers her face with white mask. The part that surprised me is that the algorithm makes decision based on the data that has been used to train it. If the data is biased, the algorithm could also be bias. While watching it, I feel that this is a real issue and it deserves more attention being put on it.

LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMycKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICAgICAjIGZvciBncmFwaGluZyBhbmQgZGF0YSBjbGVhbmluZwpsaWJyYXJ5KHRpZHltb2RlbHMpICAgICAgICAjIGZvciBtb2RlbGluZwpsaWJyYXJ5KHRoZW1pcykgIApsaWJyYXJ5KGRvUGFyYWxsZWwpICAgICAgICAjIGZvciBwYXJhbGxlbCBwcm9jZXNzaW5nCmxpYnJhcnkoc3RhY2tzKSAgICAgICAgICAgICMgZm9yIHN0YWNraW5nIG1vZGVscwpsaWJyYXJ5KG5hbmlhcikgICAgICAgICAgICAjIGZvciBleGFtaW5pbmcgbWlzc2luZyB2YWx1ZXMgKE5BcykKbGlicmFyeShsdWJyaWRhdGUpICAgICAgICAgIyBmb3IgZGF0ZSBtYW5pcHVsYXRpb24KbGlicmFyeShtb2Rlcm5kaXZlKSAgICAgICAgIyBmb3IgS2luZyBDb3VudHkgaG91c2luZyBkYXRhCmxpYnJhcnkodmlwKSAgICAgICAgICAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKbGlicmFyeShwYXRjaHdvcmspICAgICAgICAgIyBmb3IgY29tYmluaW5nIHBsb3RzIG5pY2VseQpsaWJyYXJ5KHJhbmdlcikKbGlicmFyeSh4Z2Jvb3N0KQpgYGAKCmBgYHtyfQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpgYGAKCmBgYHtyfQpkYXRhKCJsZW5kaW5nX2NsdWIiKQojIERhdGEgZGljdGlvbmFyeSAoYXMgY2xvc2UgYXMgSSBjb3VsZCBmaW5kKTogaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS93b3Jkc2ZvcnRoZXdpc2UvbGVuZGluZy1jbHViL2Rpc2N1c3Npb24vMTcwNjkxCmBgYAoKIyMgR2l0SHViIAoKaHR0cHM6Ly9naXRodWIuY29tL3Npdmh1by9hZHYtZGF0YS1zY2llbmNlLWh3My5naXQKCiMjIE1vZGVsaW5nIAoKV2XigJlsbCBiZSB1c2luZyB0aGUgYGxlbmRpbmdfY2x1YmAgZGF0YXNldCBmcm9tIHRoZSBgbW9kZWxkYXRhYCBsaWJyYXJ5LCB3aGljaCBpcyBwYXJ0IG9mIGB0aWR5bW9kZWxzYC4gVGhlIGRhdGEgZGljdGlvbmFyeSB0aGV5IHJlZmVyZW5jZSBkb2VzbuKAmXQgc2VlbSB0byBleGlzdCBhbnltb3JlLCBidXQgaXQgc2VlbXMgdGhlIG9uZSBvbiB0aGlzIGthZ2dsZSBkaXNjdXNzaW9uIGlzIHByZXR0eSBjbG9zZS4gSXQgbWlnaHQgYWxzbyBoZWxwIHRvIHJlYWQgYSBiaXQgYWJvdXQgTGVuZGluZyBDbHViIGJlZm9yZSBzdGFydGluZyBpbiBvbiB0aGUgZXhlcmNpc2VzLgoKVGhlIG91dGNvbWUgd2UgYXJlIGludGVyZXN0ZWQgaW4gcHJlZGljdGluZyBpcyBgQ2xhc3NgLiBBbmQgYWNjb3JkaW5nIHRvIHRoZSBkYXRhc2V04oCZcyBoZWxwIHBhZ2UsIGl0cyB2YWx1ZXMgYXJlIOKAnGVpdGhlciDigJhnb29k4oCZIChtZWFuaW5nIHRoYXQgdGhlIGxvYW4gd2FzIGZ1bGx5IHBhaWQgYmFjayBvciBjdXJyZW50bHkgb24tdGltZSkgb3Ig4oCYYmFk4oCZIChjaGFyZ2VkIG9mZiwgZGVmYXVsdGVkLCBvciAyMS0xMjAgZGF5cyBsYXRlKeKAnS4KCiMjIyBUYXNrczogCjEuIEV4cGxvcmUgdGhlIGRhdGEsIGNvbmNlbnRyYXRpbmcgb24gZXhhbWluaW5nIGRpc3RyaWJ1dGlvbnMgb2YgdmFyaWFibGVzIGFuZCBleGFtaW5pbmcgbWlzc2luZyB2YWx1ZXMuCgpUaGlzIGRhdGFzZXQgaGFzIDIzIHZhcmlhYmxlcy4gV2UgYXJlIGdvaW5nIHRvIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBxdWFudGl0YXRpdmUgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gCgpgYGB7cn0KbGVuZGluZ19jbHViICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiKQpgYGAKQWNjb3JkaW5nIHRvIHRoZSBncmFwaHMgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgbWFueSB2YXJpYWJsZXMgdGhhdCBhcmUgcmlnaHQgc2tld2VkIHN1Y2ggYXMgYW5udWFsX2luYywgaW5xX2xhc3RfMTJtLCBudW1faWxfdGwsIG9wZW5faWxfMjRtLCBvcGVuX2lsXzZtLCB0b3RhbF9iYWxfaWwsIGFuZCB0b3RhbF9pbF9oaWdoX2NyZWRpdF9saS4gCgoKYGBge3J9CmxlbmRpbmdfY2x1YiAlPiUgCiAgc2VsZWN0KHdoZXJlKGlzLmZhY3RvcikpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21fYmFyKCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCAKICAgICAgICAgICAgIG5yb3cgPSAyKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGVyZSBhcmUgNiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuIFRoZXNlIHZhcmlhYmxlcyBhcmUgYWxsIHdlbGwgZGlzdHJpYnV0ZWQuIElmIHdlIGxvb2sgYXQgdGhlICdDbGFzcycgdmFyaWFibGUsIG1vc3Qgb2YgdGhlIGRhdGEgcG9pbnRzIGFyZSBpbiBnb29kIGNhdGVnb3J5LiAKCjIuIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3QsIHB1dHRpbmcgNzUlIGluIHRoZSB0cmFpbmluZyBkYXRhLiBTdHJhdGlmeSBieSBDbGFzcyAoYWRkIHN0cmF0YSA9Q2xhc3N0byB0aGVpbml0aWFsX3NwbGl0KClgIGZ1bmN0aW9uKS4KCmBgYHtyfQpzZXQuc2VlZCg0OTQpICMgZm9yIHJlcHJvZHVjaWJpbGl0eQoKIyByZW1vdmUgdGhlICMncyBvbmNlIHlvdSd2ZSBkZWZpbmVkIHRoZXNlIC0gdGhpcyBpcyBzbyB3ZSBhbGwgaGF2ZSB0aGUgc2FtZSBuYW1lCmxlbmRpbmdfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChsZW5kaW5nX2NsdWIsIHN0cmF0YSA9ICdDbGFzcycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvcCA9IC43NSkKCmxlbmRpbmdfdHJhaW5pbmcgPC0gdHJhaW5pbmcobGVuZGluZ19zcGxpdCkKbGVuZGluZ190ZXN0IDwtIHRlc3RpbmcobGVuZGluZ19zcGxpdCkKYGBgCgozLiBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgbGFzc28gbW9kZWwuIFNvbWUgc3RlcHMgeW91IHNob3VsZCB0YWtlOgoKICAtIFVzZSBgc3RlcF91cHNhbXBsZSgpYCBmcm9tIHRoZSBgdGhlbWlzYCBsaWJyYXJ5IHRvIHVwc2FtcGxlIHRoZSDigJxiYWTigJ0gY2F0ZWdvcnkgc28gdGhhdCBpdCBpcyA1MCUgb2YgdGhlIOKAnGdvb2TigJ0gY2F0ZWdvcnkuIERvIHRoaXMgYnkgc2V0dGluZyBgb3Zlcl9yYXRpbyA9IC41YC4KICAtIFVzZSBgc3RlcF9kb3duc2FtcGxlKClgIGZyb20gdGhlIGB0aGVtaXNgIGxpYnJhcnkgdG8gZG93bnNhbXBsZSB0aGUg4oCcZ29vZOKAnSBjYXRlZ29yeSBzbyB0aGUgYmFkcyBhbmQgZ29vZHMgYXJlIGV2ZW4gLSBzZXQgYHVuZGVyX3JhdGlvID0gMWAuIE1ha2Ugc3VyZSB0byBkbyB0aGlzIHN0ZXAgQUZURVIgYHN0ZXBfdXBzYW1wbGUoKWAuCiAgLSBNYWtlIGFsbCBpbnRlZ2VyIHZhcmlhYmxlcyBudW1lcmljIChJ4oCZZCBoaWdobHkgcmVjb21tZW5kIHVzaW5nIGBzdGVwX211dGF0ZV9hdCgpYCBhbmQgdXNpbmcgdGhlIGBhbGxfbnVtZXJpYygpYCBoZWxwZXIgb3IgdGhpcyB3aWxsIGJlIGEgbG90IG9mIGNvZGUpLiBUaGlzIHN0ZXAgbWlnaHQgc2VlbSByZWFsbHkgd2VpcmQgcmlnaHQgbm93LCBidXQgd2XigJlsbCB3YW50IHRvIGRvIHRoaXMgZm9yIHRoZSBtb2RlbCBpbnRlcnByZXRhdGlvbiB3ZeKAmWxsIGRvIGluIGEgbGF0ZXIgYXNzaWdubWVudC4KICAtIFRoaW5rIGFib3V0IGdyb3VwaW5nIGZhY3RvciB2YXJpYWJsZXMgd2l0aCBtYW55IGxldmVscy4KICAtIE1ha2UgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGR1bW15IHZhcmlhYmxlcyAobWFrZSBzdXJlIE5PVCB0byBkbyB0aGlzIHRvIHRoZSBvdXRjb21lIHZhcmlhYmxlKS4KICAtIE5vcm1hbGl6ZSBxdWFudGl0YXRpdmUgdmFyaWFibGVzLgogIApPbmNlIHlvdSBoYXZlIHRoYXQsIHVzZSBgcHJlcCgpYCwgYGp1aWNlKClgLCBhbmQgYGNvdW50KClgIHRvIGNvdW50IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIGVhY2ggY2xhc3MuIFRoZXkgc2hvdWxkIGJlIGVxdWFsLiBUaGlzIGRhdGFzZXQgd2lsbCBiZSB1c2VkIGluIGJ1aWxkaW5nIHRoZSBtb2RlbCwgYnV0IHRoZSBkYXRhIHdpdGhvdXQgdXAgYW5kIGRvd24gc2FtcGxpbmcgd2lsbCBiZSB1c2VkIGluIGV2YWx1YXRpb24uCgpgYGB7cn0Kc2V0LnNlZWQoNDU2KQoKbGFzc29fcmVjaXBlIDwtIHJlY2lwZShDbGFzcyB+IC4sIGRhdGEgPSBsZW5kaW5nX3RyYWluaW5nKSAlPiUgCiAgc3RlcF91cHNhbXBsZShDbGFzcywgb3Zlcl9yYXRpbyA9IDAuNSkgJT4lIAogIHN0ZXBfZG93bnNhbXBsZShDbGFzcywgdW5kZXJfcmF0aW8gPSAxKSAlPiUgCiAgc3RlcF9tdXRhdGVfYXQoYWxsX251bWVyaWMoKSwgZm4gPSB+YXMubnVtZXJpYyguKSkgJT4lIAogIHN0ZXBfbXV0YXRlKHN1Yl9ncmFkZSA9IGFzLmNoYXJhY3RlcihzdWJfZ3JhZGUpLCAKICAgICAgICAgICAgICBncmFkZSA9IGFzLmZhY3RvcihzdHJfc3ViKHN1Yl9ncmFkZSwxLDEpKSklPiUgCiAgc3RlcF9ybShzdWJfZ3JhZGUpICU+JSAKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lIAogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkKYGBgCgpgYGB7cn0KbGFzc29fcmVjaXBlICU+JSAKICBwcmVwKGxlbmRpbmdfdHJhaW5pbmcpICU+JQogIGp1aWNlKCkgCmBgYAo0LiBTZXQgdXAgdGhlIGBsYXNzb2AgbW9kZWwgYW5kIHdvcmtmbG93LiBXZSB3aWxsIHR1bmUgdGhlIGBwZW5hbHR5YCBwYXJhbWV0ZXIuCgpgYGB7cn0KbGFzc29fbW9kIDwtICAKICBsb2dpc3RpY19yZWcobWl4dHVyZSA9IDEpICU+JSAKICBzZXRfZW5naW5lKCJnbG1uZXQiKSAlPiUgCiAgc2V0X2FyZ3MocGVuYWx0eSA9IHR1bmUoKSkgJT4lIAogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpCgpsYXNzb193ZiA8LSAKICB3b3JrZmxvdygpICU+JSAKICBhZGRfcmVjaXBlKGxhc3NvX3JlY2lwZSkgJT4lIAogIGFkZF9tb2RlbChsYXNzb19tb2QpCgpsYXNzb193ZgpgYGAKCjUuIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciB0aGUgYHBlbmFsdHlgIHBhcmFtZXRlci4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIEZpbmQgdGhlIGFjY3VyYWN5IGFuZCBhcmVhIHVuZGVyIHRoZSByb2MgY3VydmUgZm9yIHRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXIuIFVzZSA1LWZvbGQgY3YuCgpgYGB7cn0Kc2V0LnNlZWQoNDk0KSAKbGVuZGluZ19jdiA8LSB2Zm9sZF9jdihsZW5kaW5nX3RyYWluaW5nLCB2ID0gNSkKCnBlbmFsdHlfZ3JpZCA8LSBncmlkX3JlZ3VsYXIocGVuYWx0eSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDEwKQoKY3RybF9ncmlkIDwtIGNvbnRyb2xfc3RhY2tfZ3JpZCgpCgpsYXNzb190dW5lIDwtIGxhc3NvX3dmICU+JSAKICB0dW5lX2dyaWQoCiAgICByZXNhbXBsZXMgPSBsZW5kaW5nX2N2LAogICAgZ3JpZCA9IHBlbmFsdHlfZ3JpZCwgCiAgICBjb250cm9sID0gY3RybF9ncmlkKQpgYGAKCkhlcmUgd2UgZmluZCB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsOiAKCmBgYHtyfQpsYXNzb19hY2MgPC0gbGFzc29fdHVuZSAlPiUgCiAgc2hvd19iZXN0KCJhY2N1cmFjeSIpICU+JSAKICBmaWx0ZXIocGVuYWx0eSA9PSAobGFzc29fdHVuZSAlPiUgIHNlbGVjdF9iZXN0KCJhY2N1cmFjeSIpKSRwZW5hbHR5KQpsYXNzb19hY2MKYGBgCgpIZXJlIGlzIHRoZSBST0MgY3VydmUgbWV0cmljOiAKCmBgYHtyfQpsYXNzb19yb2MgPC0gbGFzc29fdHVuZSAlPiUgCiAgc2hvd19iZXN0KCJyb2NfYXVjIikgJT4lIAogIGZpbHRlcihwZW5hbHR5ID09IChsYXNzb190dW5lICU+JSAgc2VsZWN0X2Jlc3QoInJvY19hdWMiKSkkcGVuYWx0eSkKbGFzc29fcm9jCmBgYAoKVXNpbmcgYWNjdXJhY3kgbWV0cmljLCB3ZSBnZXQgMC43MjI5IGZvciBvdXIgbWVhbiB3aXRoIFByZXByb2Nlc3NvcjFfTW9kZWwwOSwgYW5kIHVzaW5nIFJPQyBjdXJ2ZSBtZXRyaWMsIHdlIGdldCBzbGlnaHRseSBoaWdoZXIgZm9yIG91ciBtZWFuIHdoaWNoIGlzIDAuNzM1MiBmb3IgUHJlcHJvY2Vzc29yMV9Nb2RlbDA5IHRvby4gCgo2LiBTZXQgdXAgdGhlIHJlY2lwZSBhbmQgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIHRvIGJ1aWxkIGEgcmFuZG9tIGZvcmVzdCBtb2RlbC4gWW91IHNob3VsZG7igJl0IGhhdmUgdG8gZG8gYXMgbWFueSBzdGVwcy4gVGhlIG9ubHkgc3RlcHMgeW91IHNob3VsZCBuZWVkIHRvIGRvIGFyZSBtYWtpbmcgYWxsIGludGVnZXJzIG51bWVyaWMgYW5kIHRoZSB1cCBhbmQgZG93biBzYW1wbGluZy4KCmBgYHtyfQpzZXQuc2VlZCg0NTYpCnJmX3JlY2lwZSA8LSByZWNpcGUoQ2xhc3MgfiAuLCBkYXRhID0gbGVuZGluZ190cmFpbmluZykgJT4lIAogIHN0ZXBfdXBzYW1wbGUoQ2xhc3MsIG92ZXJfcmF0aW8gPSAwLjUpICU+JSAKICBzdGVwX2Rvd25zYW1wbGUoQ2xhc3MsIHVuZGVyX3JhdGlvID0gMSkgJT4lIAogIHN0ZXBfbXV0YXRlX2F0KGFsbF9udW1lcmljKCksIGZuID0gfmFzLm51bWVyaWMoLikpIApgYGAKCjcuIFNldCB1cCB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBhbmQgd29ya2Zsb3cuIFdlIHdpbGwgdHVuZSB0aGUgYG10cnlgIGFuZCBgbWluX25gIHBhcmFtZXRlcnMgYW5kIHNldCB0aGUgbnVtYmVyIG9mIGB0cmVlc2AsIHRyZWVzLCB0byAxMDAgKG90aGVyd2lzZSB0aGUgbmV4dCBzdGVwcyB0YWtlIHRvbyBsb25nKS4KCmBgYHtyfQpyZl9tb2RlbCA8LSByYW5kX2ZvcmVzdCgKICAgICAgICAgICAgICBtdHJ5ID0gdHVuZSgpLCAKICAgICAgICAgICAgICBtaW5fbiA9IHR1bmUoKSwgCiAgICAgICAgICAgICAgdHJlZXMgPSAxMDApICU+JSAKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUKICBzZXRfZW5naW5lKCJyYW5nZXIiKQoKcmZfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKHJmX3JlY2lwZSkgJT4lCiAgYWRkX21vZGVsKHJmX21vZGVsKQpgYGAKCjguIFNldCB1cCB0aGUgbW9kZWwgdHVuaW5nIGZvciBib3RoIHRoZSBgbXRyeWAgYW5kIGBtaW5fbmAgcGFyYW1ldGVycy4gQmUgc3VyZSB0byBhZGQgdGhlIGBjb250cm9sX3N0YWNrX2dyaWQoKWAgZm9yIHRoZSBgY29udHJvbGAgYXJndW1lbnQgc28gd2UgY2FuIHVzZSB0aGVzZSByZXN1bHRzIGxhdGVyIHdoZW4gd2Ugc3RhY2suIFVzZSBvbmx5IDMgbGV2ZWxzIGluIHRoZSBncmlkLiBGb3IgdGhlIGBtdHJ5YCBwYXJhbWV0ZXIsIHlvdSBuZWVkIHRvIHB1dCBgZmluYWxpemUobXRyeSgpLCBsZW5kaW5nX3RyYWluaW5nICU+JSBzZWxlY3QoLUNsYXNzKSlgIGluIGFzIGFuIGFyZ3VtZW50IGluc3RlYWQgb2YganVzdCBgbXRyeSgpYCwgd2hlcmUgYGxlbmRpbmdfdHJhaW5pbmdgIGlzIHRoZSBuYW1lIG9mIHlvdXIgdHJhaW5pbmcgZGF0YS4gVGhpcyBpcyBiZWNhdXNlIHRoZSBgbXRyeSgpYCBncmlkIHdpbGwgb3RoZXJ3aXNlIGhhdmUgdW5rbm93bnMgaW4gaXQuIFRoaXMgcGFydCBjYW4gdGFrZSBhIHdoaWxlIHRvIHJ1bi4KCmBgYHtyfQpib29zdF9tdHJ5X2FuZF9taW5fbiA8LSBncmlkX3JlZ3VsYXIoZmluYWxpemUobXRyeSgpLCBsZW5kaW5nX3RyYWluaW5nICU+JSBzZWxlY3QoLUNsYXNzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9uKCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IDMpCmJvb3N0X210cnlfYW5kX21pbl9uCmBgYAoKYGBge3J9CmN0cmxfZ3JpZCA8LSBjb250cm9sX3N0YWNrX2dyaWQoKQpyZl90dW5lIDwtIAogIHJmX3dvcmtmbG93ICU+JSAKICB0dW5lX2dyaWQoCiAgcmVzYW1wbGVzID0gbGVuZGluZ19jdiAsCiAgZ3JpZCA9IGJvb3N0X210cnlfYW5kX21pbl9uLAogIGNvbnRyb2wgPSBjdHJsX2dyaWQKKQpgYGAKCjkuIEZpbmQgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlcnMuIFdoYXQgYXJlIHRoZSBhY2N1cmFjeSBhbmQgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIGZvciB0aGUgbW9kZWwgd2l0aCB0aG9zZSB0dW5pbmcgcGFyYW1ldGVycz8KCkhlcmUgaXMgdGhlIHJlc3VsdCB1c2luZyBhY2N1cmFjeSBtZXRyaWM6IAoKYGBge3J9CnJmX2FjYyA8LSByZl90dW5lICU+JSAKICBzaG93X2Jlc3QoImFjY3VyYWN5IikgJT4lIAogIGZpbHRlcihtdHJ5ID09IChyZl90dW5lICU+JSAgc2VsZWN0X2Jlc3QoImFjY3VyYWN5IikpJG10cnksIAogICAgICAgbWluX24gPT0gKHJmX3R1bmUgJT4lICBzZWxlY3RfYmVzdCgiYWNjdXJhY3kiKSkkbWluX24pCnJmX2FjYwpgYGAKCkhlcmUgaXMgdGhlIHJlc3VsdCB1c2luZyBST0MgY3VydmUgbWV0cmljOiAKCmBgYHtyfQpyZl9yb2MgPC0gcmZfdHVuZSAlPiUgCiAgc2hvd19iZXN0KCJyb2NfYXVjIikgJT4lIAogIGZpbHRlcihtdHJ5ID09IChyZl90dW5lICU+JSAgc2VsZWN0X2Jlc3QoInJvY19hdWMiKSkkbXRyeSwgCiAgICAgICBtaW5fbiA9PSAocmZfdHVuZSAlPiUgIHNlbGVjdF9iZXN0KCJyb2NfYXVjIikpJG1pbl9uKQoKcmZfcm9jCmBgYApCYXNlZCBvbiB0aGUgdHdvIHRhYmxlcyBhYm92ZSwgd2UgY2FuIHNlZSB0aGF0IHdpdGggYWNjdXJhY3kgbWV0cmljLCB0aGUgYmVzdCBhY2N1cmFjeSBpcyA5Mi44NCUuIEhvd2V2ZXIsIHRoZSBST0MgY3VydmUgbWV0cmljcyBzaG93cyBhIHNsaWdodGx5IGxvd2VyIGFjY3VyYWN5IHdoaWNoIGlzIG9ubHkgNzMuNSUuIAoKMTAuIE5leHQsIHdlIHdpbGwgZml0IGEgYm9vc3RlZCB0cmVlIHVzaW5nIHhnYm9vc3QuIFdlIHdpbGwgb25seSB0dW5lIHRoZSBgbGVhcm5fcmF0ZWAgcGFyYW1ldGVyLiBJIGhhdmUgc3BlY2lmaWVkIHRoZSBtb2RlbCwgcmVjaXBlLCBhbmQgd29ya2Zsb3cgYmVsb3cgYWxyZWFkeSAodW5jb21tZW50IHRoZSBjb2RlIC0geW91IGNhbiB0aGlzIGJ5IGhpZ2hsaWdodGluZyBpdCBhbmQgdGhlbiBpbiB0aGUgY29kZSB0YWIgYXQgdGhlIHRvcCwgY2hvb3NlIGNvbW1lbnQvdW5jb21tZW50IGxpbmVzKS4gWW91IG5lZWQgdG8gc2V0IHVwIGEgZ3JpZCBvZiB0ZW4gdmFsdWVzIGZvciB0aGUgdHVuaW5nIHBhcmFtZXRlciBhbmQgdHVuZSB0aGUgbW9kZWwuIEJlIHN1cmUgdG8gYWRkIHRoZSBgY29udHJvbF9zdGFja19ncmlkKClgIGZvciB0aGUgYGNvbnRyb2xgIGFyZ3VtZW50IHNvIHdlIGNhbiB1c2UgdGhlc2UgcmVzdWx0cyBsYXRlciB3aGVuIHdlIHN0YWNrLgoKYGBge3J9CnhnYm9vc3Rfc3BlYyA8LQogIGJvb3N0X3RyZWUoCiAgICB0cmVlcyA9IDEwMDAsCiAgICBtaW5fbiA9IDUsCiAgICB0cmVlX2RlcHRoID0gMiwKICAgIGxlYXJuX3JhdGUgPSB0dW5lKCksCiAgICBsb3NzX3JlZHVjdGlvbiA9IDEwXi01LAogICAgc2FtcGxlX3NpemUgPSAxKSAlPiUKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUKICBzZXRfZW5naW5lKCJ4Z2Jvb3N0IikKCnhnYm9vc3RfcmVjaXBlIDwtIHJlY2lwZShmb3JtdWxhID0gQ2xhc3MgfiAuLCBkYXRhID0gbGVuZGluZ190cmFpbmluZykgJT4lCiAgc3RlcF91cHNhbXBsZShDbGFzcywgb3Zlcl9yYXRpbyA9IC41KSAlPiUKICBzdGVwX2Rvd25zYW1wbGUoQ2xhc3MsIHVuZGVyX3JhdGlvID0gMSkgJT4lCiAgc3RlcF9tdXRhdGVfYXQoYWxsX251bWVyaWMoKSwKICAgICAgICAgICAgICAgICBmbiA9IH5hcy5udW1lcmljKC4pKSAlPiUKICBzdGVwX25vdmVsKGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCksIG9uZV9ob3QgPSBUUlVFKSAlPiUKICBzdGVwX3p2KGFsbF9wcmVkaWN0b3JzKCkpCgp4Z2Jvb3N0X3dvcmtmbG93IDwtCiAgd29ya2Zsb3coKSAlPiUKICBhZGRfcmVjaXBlKHhnYm9vc3RfcmVjaXBlKSAlPiUKICBhZGRfbW9kZWwoeGdib29zdF9zcGVjKQoKc2V0LnNlZWQoNDk0KQpyZWdpc3RlckRvUGFyYWxsZWwoKSAKCmJvb3N0X2xlYXJuX3JhdGU8LSBncmlkX3JlZ3VsYXIobGVhcm5fcmF0ZSgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSAzKQoKYm9vc3RfdHVuZSA8LSB4Z2Jvb3N0X3dvcmtmbG93ICU+JSAKICB0dW5lX2dyaWQoCiAgcmVzYW1wbGVzID0gbGVuZGluZ19jdiAsCiAgZ3JpZCA9IGJvb3N0X2xlYXJuX3JhdGUsCiAgY29udHJvbCA9IGN0cmxfZ3JpZAopCgpgYGAKCjExLiBGaW5kIHRoZSBiZXN0IHR1bmluZyBwYXJhbWV0ZXJzLiBXaGF0IGFyZSB0aGUgYWNjdXJhY3kgYW5kIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSBmb3IgdGhlIG1vZGVsIHdpdGggdGhvc2UgdHVuaW5nIHBhcmFtZXRlcnM/CgpCZXN0IHR1bmluZyBwYXJhbWV0ZXIgZm9yIGFjY3VyYWN5OiAKCmBgYHtyfQpiZXN0X3BhcmFtX3hnYm9vc3RfYWNjIDwtIGJvb3N0X3R1bmUgJT4lIAogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJhY2N1cmFjeSIpICU+JSAKICBmaWx0ZXIobGVhcm5fcmF0ZSA9PSAoYm9vc3RfdHVuZSAlPiUgIHNlbGVjdF9iZXN0KCJhY2N1cmFjeSIpKSRsZWFybl9yYXRlKQpiZXN0X3BhcmFtX3hnYm9vc3RfYWNjCmBgYAoKQmVzdCB0dXJuaW5nIHBhcmFtZXRlciBmb3IgUk9DIGN1cnZlOiAKCmBgYHtyfQpiZXN0X3BhcmFtX3hnYm9vc3Rfcm9jX2N1cnZlIDwtIGJvb3N0X3R1bmUgJT4lIAogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJyb2NfYXVjIikgJT4lIAogIGZpbHRlcihsZWFybl9yYXRlID09IChib29zdF90dW5lICU+JSAgc2VsZWN0X2Jlc3QoInJvY19hdWMiKSkkbGVhcm5fcmF0ZSkKYmVzdF9wYXJhbV94Z2Jvb3N0X3JvY19jdXJ2ZQpgYGAKV2UgY2FuIHNlZSB0aGF0IHRoZSBsZWFybl9yYXRlIGZvciBhY2N1cmFjeSBpcyAwLjEsIHdoaWxlIHRoZSBsZWFybl9yYXRlIGZvciB0aGUgUk9DIGN1cnZlIGlzIDMuMTYyMjc4ZS0wNi4gCgoxMi4gQ3JlYXRlIGEgbW9kZWwgc3RhY2sgd2l0aCB0aGUgY2FuZGlkYXRlIG1vZGVscyBmcm9tIHRoZSBwcmV2aW91cyBwYXJ0cyBvZiB0aGUgZXhlcmNpc2UgYW5kIHVzZSB0aGUgYGJsZW5kX3ByZWRpY3Rpb25zKClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgc3RhY2tlZCBtb2RlbC4gQ3JlYXRlIGEgcGxvdCBleGFtaW5pbmcgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIHRoZSBkaWZmZXJlbnQgcGVuYWx0eSBwYXJhbWV0ZXJzIHRvIGFzc3VyZSB5b3UgaGF2ZSBjYXB0dXJlZCB0aGUgYmVzdCBvbmUuIElmIG5vdCwgYWRqdXN0IHRoZSBwZW5hbHR5LiAoSElOVDogdXNlIHRoZSBgYXV0b3Bsb3QoKWAgZnVuY3Rpb24pLiBXaGljaCBtb2RlbHMgYXJlIGNvbnRyaWJ1dGluZyBtb3N0PwoKYGBge3J9CmxlbmRpbmdfc3RhY2sgPC0gCiAgc3RhY2tzKCkgJT4lIAogIGFkZF9jYW5kaWRhdGVzKGxhc3NvX3R1bmUpICU+JSAKICBhZGRfY2FuZGlkYXRlcyhyZl90dW5lKSAlPiUgCiAgYWRkX2NhbmRpZGF0ZXMoYm9vc3RfdHVuZSkKYGBgCgpgYGB7cn0KYXNfdGliYmxlKGxlbmRpbmdfc3RhY2spCmBgYAoKYGBge3J9CmxlbmRpbmdfYmxlbmQgPC0gbGVuZGluZ19zdGFjayAlPiUgCiAgYmxlbmRfcHJlZGljdGlvbnMoKQoKbGVuZGluZ19ibGVuZApgYGAKCmBgYHtyfQphdXRvcGxvdChsZW5kaW5nX2JsZW5kLCB0eXBlID0gIm1lbWJlcnMiKQpgYGAKCmBgYHtyfQphdXRvcGxvdChsZW5kaW5nX2JsZW5kLCB0eXBlID0gIndlaWdodHMiKQpgYGAKCkZyb20gdGhlIGdyYXBoIGFib3ZlLCB3ZSBjYW4gc2VlIHRoYXQgYm9vc3RfdHJlZSBjb250cmlidXRlcyB0aGUgbW9zdCB0byB0aGUgbW9kZWwgd2l0aCBzdGFja2luZyBjb2VmZmljaWVudCA9IDI1MC4gCgoxMy4gRml0IHRoZSBmaW5hbCBzdGFja2VkIG1vZGVsIHVzaW5nIGBmaXRfbWVtYmVycygpYC4gQXBwbHkgdGhlIG1vZGVsIHRvIHRoZSB0cmFpbmluZyBkYXRhLiBDb21wdXRlIHRoZSBhY2N1cmFjeSwgY29uc3RydWN0IGEgY29uZnVzaW9uIG1hdHJpeCwgYW5kIGNyZWF0ZSBhIGRlbnNpdHkgcGxvdCB3aXRoIGAucHJlZF9nb29kYCBvbiB0aGUgeC1heGlzICh0aGUgcHJvYmFiaWxpdHkgb2YgYSByZXNwb25zZSBvZiDigJxnb29k4oCdKSwgZmlsbGVkIGJ5IGBDbGFzc2AuIENvbW1lbnQgb24gd2hhdCB5b3Ugc2VlLgoKYGBge3J9CmxlbmRpbmdfZmluYWxfc3RhY2sgPC0gbGVuZGluZ19ibGVuZCAlPiUgCiAgZml0X21lbWJlcnMoKQpgYGAKCmBgYHtyfQpsZW5kaW5nX2ZpbmFsX2dvb2RfYmFkIDwtIGxlbmRpbmdfZmluYWxfc3RhY2sgJT4lIAogIHByZWRpY3QobmV3X2RhdGEgPSBsZW5kaW5nX3RyYWluaW5nKQpgYGAKCmBgYHtyfQpsZW5kaW5nX2ZpbmFsX3ByZWRpY3Rpb24gPC0gbGVuZGluZ19maW5hbF9zdGFjayAlPiUgCiAgcHJlZGljdChuZXdfZGF0YSA9IGxlbmRpbmdfdHJhaW5pbmcsIHR5cGUgPSAicHJvYiIpICU+JSAKICBiaW5kX2NvbHMobGVuZGluZ190cmFpbmluZykgJT4lIAogIGJpbmRfY29scyhsZW5kaW5nX2ZpbmFsX2dvb2RfYmFkKQoKbGVuZGluZ19maW5hbF9wcmVkaWN0aW9uCmBgYAoKYGBge3J9CmxlbmRpbmdfZmluYWxfcHJlZGljdGlvbiAlPiUKICBjb25mX21hdCh0cnV0aCA9IENsYXNzLCBlc3RpbWF0ZSA9IC5wcmVkX2NsYXNzKQpgYGAKClRydWUgcG9zaXRpdmUgcmF0ZTogNzAwOS8oNzAwOSszODMpID0gMC45NDgKVHJ1ZSBuZWdhdGl2ZSByYXRlOiAwLygwKzApID0gMC8wIAoKVGhlIGFjY3VyYWN5IHdpbGwgYmU6IDcwMDkvKDcwMDkrMzgzKSA9IDAuOTQ4IAoKV2UgYXJlIGdvaW5nIHRvIGNyZWF0ZSBhIGRlbnNpdHkgb2YgcGxvdCBvZiBwcmVkX2dvb2QgY2F0ZWdvcml6ZWQgYnkgY2xhc3MuIAoKYGBge3J9CmxlbmRpbmdfZmluYWxfcHJlZGljdGlvbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSAucHJlZF9nb29kLCBmaWxsID0gQ2xhc3MpKSArIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNSwgY29sb3IgPSBOQSkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIG1ham9yaXR5IG9mIGBDbGFzc2AgaXMgcHJlZGljdGVkIGFzIGdvb2QuIFRoZXJlIGlzIG9ubHkgYSBzbWFsbCBhcmVhIHRoYXQgYXJlIG92ZXJsYXBwZWQgc28gdGhlIGFjY3VyYWN5IHdpbGwgYmUgZmFpcmx5IGhpZ2guIEFuZCB0aGUgYmxlbmVkIG1vZGVsIGRvZXNuJ3QgcHJlZGljdCBhbnkgYmFkIGNsYXNzIGFzIHRoZSBudW1iZXIgb2YgYmFkIGNsYXNzIGlzIGZhaXJseSBzbWFsbCBjb21wYXJlZCB0byBnb29kIGNsYXNzLiAKCjE0LiBJbiB0aGUgcHJldmlvdXMgcHJvYmxlbSwgeW91IHNhdyB0aGF0IGFsdGhvdWdoIHRoZSBhY2N1cmFjeSB3YXMgcXVpdGUgaGlnaCwgdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSAoYWthIHNlbnNpdGl2aXR5KSB3YXMgdGVycmlibGUuIEl04oCZcyBjb21tb24gdG8gc2VlIHRoaXMgd2hlbiBvbmUgb2YgdGhlIGNsYXNzZXMgaGFzIGxvdyByZXByZXNlbnRhdGlvbi4gV2hhdCB3ZSB3YW50IHRvIGRvIG5vdyBpcyBpbnZlc3RpZ2F0ZSB3aGF0IGhhcHBlbnMgaW4gZWFjaCBvZiBvdXIgbW9kZWxzLiBCZWxvdyBJ4oCZdmUgcHJvdmlkZWQgY29kZSB0byBpbnZlc3RpZ2F0ZSB0aGUgbGFzc28gbW9kZWwgKHdoZXJlIGBsYXNzb190dW5lYCBpcyB0aGUgbmFtZSBvZiBteSB0dW5pbmcgc3RlcCkuIERvIHNpbWlsYXIgdGhpbmdzIGZvciB0aGUgcmFuZG9tIGZvcmVzdCBhbmQgeGdib29zdCBtb2RlbHMuIElmIHlvdeKAmWQgbGlrZSB0byBoYXZlIGEgYmV0dGVyIHRydWUgbmVnYXRpdmUgcmF0ZSwgd2hpY2ggbW9kZWxzIHdvdWxkIHlvdSBjaG9vc2UgYW5kIGhvdyB3b3VsZCB5b3UgZ28gYWJvdXQgZG9pbmcgdGhpcyBpbiBhIGxlc3MgbWFudWFsIHdheSAoeW91IGRvbuKAmXQgbmVlZCB0byB3cml0ZSBjb2RlIHRvIGRvIGl0IC0ganVzdCBkZXNjcmliZSBpdCBpbiB3b3JkcykuIEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAgd2hlbiB5b3UgYXJlIGZpbmlzaGVkLgoKYGBge3J9Cmxhc3NvX3R1bmUgJT4lIAogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUgCiAgZ3JvdXBfYnkoaWQsIHBlbmFsdHkpICU+JSAKICBzdW1tYXJpemUoYWNjdXJhY3kgPSBzdW0oKENsYXNzID09IC5wcmVkX2NsYXNzKSkvbigpLAogICAgICAgICAgICB0cnVlX25lZ19yYXRlID0gc3VtKENsYXNzID09ICJiYWQiICYgLnByZWRfY2xhc3MgPT0gImJhZCIpL3N1bShDbGFzcyA9PSAiYmFkIiksCiAgICAgICAgICAgIHRydWVfcG9zX3JhdGUgPSBzdW0oQ2xhc3MgPT0gImdvb2QiICYgLnByZWRfY2xhc3MgPT0gImdvb2QiKS9zdW0oQ2xhc3MgPT0gImdvb2QiKSkgJT4lIAogIGdyb3VwX2J5KHBlbmFsdHkpICU+JSAKICBzdW1tYXJpemUoYWNyb3NzKGFjY3VyYWN5OnRydWVfcG9zX3JhdGUsIG1lYW4pKQpgYGAKCmBgYHtyfQojIyMgUmFuZG9tIGZvcmVzdApyZl90dW5lICU+JSAKICBjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lIAogIGdyb3VwX2J5KGlkLCBtdHJ5LCBtaW5fbikgJT4lIAogIHN1bW1hcml6ZShhY2N1cmFjeSA9IHN1bSgoQ2xhc3MgPT0gLnByZWRfY2xhc3MpKS9uKCksCiAgICAgICAgICAgIHRydWVfbmVnX3JhdGUgPSBzdW0oQ2xhc3MgPT0gImJhZCIgJiAucHJlZF9jbGFzcyA9PSAiYmFkIikvc3VtKENsYXNzID09ICJiYWQiKSwKICAgICAgICAgICAgdHJ1ZV9wb3NfcmF0ZSA9IHN1bShDbGFzcyA9PSAiZ29vZCIgJiAucHJlZF9jbGFzcyA9PSAiZ29vZCIpL3N1bShDbGFzcyA9PSAiZ29vZCIpKSAlPiUgCiAgZ3JvdXBfYnkobXRyeSxtaW5fbikgJT4lIAogIHN1bW1hcml6ZShhY3Jvc3MoYWNjdXJhY3k6dHJ1ZV9wb3NfcmF0ZSwgbWVhbikpCmBgYAoKYGBge3J9CmJvb3N0X3R1bmUgJT4lIAogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUgCiAgZ3JvdXBfYnkoaWQsIGxlYXJuX3JhdGUpICU+JSAKICBzdW1tYXJpemUoYWNjdXJhY3kgPSBzdW0oKENsYXNzID09IC5wcmVkX2NsYXNzKSkvbigpLAogICAgICAgICAgICB0cnVlX25lZ19yYXRlID0gc3VtKENsYXNzID09ICJiYWQiICYgLnByZWRfY2xhc3MgPT0gImJhZCIpL3N1bShDbGFzcyA9PSAiYmFkIiksCiAgICAgICAgICAgIHRydWVfcG9zX3JhdGUgPSBzdW0oQ2xhc3MgPT0gImdvb2QiICYgLnByZWRfY2xhc3MgPT0gImdvb2QiKS9zdW0oQ2xhc3MgPT0gImdvb2QiKSkgJT4lIAogIGdyb3VwX2J5KGxlYXJuX3JhdGUpICU+JSAKICBzdW1tYXJpemUoYWNyb3NzKGFjY3VyYWN5OnRydWVfcG9zX3JhdGUsIG1lYW4pKQpgYGAKCkxvb2tpbmcgYXQgYWxsIHRoZSByZXN1bHQgYWJvdmUsIHdlIGNhbiBnZXQgdGhlIGhpZ2hlc3QgdHJ1ZSBuZWdhdGl2ZSByYXRlICgxKSBpZiB3ZSBjaG9vc2UgdGhlIHhnYm9vc3QgbW9kZWwgd2l0aCAxLjAwMDAwMGUtMTAgbGVhcm5fcmF0ZSwgYnV0IG91ciB0cnVlIHBvc2l0aXZlIHJhdGUgd2lsbCBiZSAwIHdoaWNoIGlzIG5vdCB3aGF0IHdlIHdhbnQuIAoKSWYgd2Ugd2FudCBhIG1vZGVsIHRoYXQgaGFzIGJvdGggZ29vZCB0cnVlIHBvc2l0aXZlIGFuZCB0cnVlIG5lZ2F0aXZlIHJhdGUsIEkgd291bGQgY2hvb3NlIHRoZSB4Z2Jvb3N0IG1vZGVsIHdpdGggMy4xNjIyNzhlLTA2IGxlYXJuX3JhdGUuIFdpdGggdGhpcyBtb2RlbCwgb3VyIHRydWUgbmVnYXRpdmUgcmF0ZSBpcyAwLjY5NTU0MTAgYW5kIHRydWUgcG9zaXRpdmUgcmF0ZSBpcyAwLjY0ODc1My4gCgpJbiBhIGxlc3MgbWFudWFsIHdheSwgSSB3b3VsZCB0cnkgdG8gb3B0aW1pemUgdGhlIHRydWUgbmVnYXRpdmUgcmF0ZS4gSG93ZXZlciwgdGhlcmUgaXMgYSB0cmFkZSBvZmYgYmV0d2VlbiB0aGUgbmVnYXRpdmUgcmF0ZSBhbmQgcG9zaXRpdmUgcmF0ZS4gSGlnaGVyIG5lZ2F0aXZlIHJhdGUgY2FuIGltcGFjdCB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIGFuZCBvdmVyYWxsIGFjY3VyYWN5LiAKCiMjIFNoaW55IEFwcCAKCkZvciB0aGlzIHdlZWssIHRoZXJlIGlzIG5vIGNvZGUgdG8gdHVybiBpbiBmb3IgdGhpcyBwYXJ0LiBZb3UgYXJlIGp1c3QgZ29pbmcgdG8gbmVlZCB0byB0aGluayBhYm91dCB0aGUgc3RlcHMgdG8gdGFrZS4KCklmIHlvdSBhcmUgbmV3IHRvIFNoaW55IGFwcHMgb3IgaXTigJlzIGJlZW4gYXdoaWxlIHNpbmNlIHlvdeKAmXZlIG1hZGUgb25lLCB2aXNpdCB0aGUgU2hpbnkgbGlua3Mgb24gb3VyIGNvdXJzZSBSZXNvdXJjZSBwYWdlLiBJIHdvdWxkIHJlY29tbWVuZCBzdGFydGluZyB3aXRoIG15IHJlc291cmNlIGJlY2F1c2UgaXQgd2lsbCBiZSB0aGUgbW9zdCBiYXNpYy4KCkV2ZXJ5b25lIHNob3VsZCB3YXRjaCB0aGUgVGhlbWluZyBTaGlueSB0YWxrIGJ5IENhcnNvbiBTaWV2ZXJ0IHNvIHlvdSBjYW4gbWFrZSB5b3VyIGFwcCBsb29rIGFtYXppbmcuCgojIyMgVGFza3M6CgpJbiB0aGUgZnV0dXJlLCB5b3UgYXJlIGdvaW5nIHRvIGNyZWF0ZSBhbiBhcHAgdGhhdCBhbGxvd3MgYSB1c2VyIHRvIGV4cGxvcmUgaG93IHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgYSBsb2FuIGJlaW5nIHBhaWQgYmFjayAob3IgbWF5YmUganVzdCB0aGUgcHJlZGljdGVkIGNsYXNzIC0gZWl0aGVyIOKAnGdvb2TigJ0gb3Ig4oCcYmFk4oCdKSBjaGFuZ2VzIGRlcGVuZGluZyBvbiB0aGUgdmFsdWVzIG9mIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzLgoKRm9yIHRoaXMgd2VlaywgSSB3YW50IHlvdSB0byBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6CgoxLiBIb3cgY2FuIHlvdSBzYXZlIGEgbW9kZWwgeW91IGJ1aWx0IHRvIHVzZSBpdCBsYXRlciAobGlrZSBpbiB0aGUgc2hpbnkgYXBwIHlvdeKAmWxsIGNyZWF0ZSk/CgpUbyBzYXZlIHRoZSBtb2RlbCBJIGJ1aWx0IHRvIHVzZSBpdCBsYXRlciwgSSB3b3VsZCB1c2UgdGhlIHN5bnRheDogc2F2ZShtb2RlbCwgZmlsZT0iLi4uIikuIAoKMi4gRm9yIHNoaW55IGFwcHMgdGhhdCBnZXQgcHVibGlzaGVkIChsaWtlIHlvdXJzIHdpbGwpLCBpdOKAmXMgdmVyeSBpbXBvcnRhbnQgdG8gaGF2ZSBBTEwgdGhlIGxpYnJhcmllcyB0aGF0IGFyZSB1c2VkIHdpdGhpbiB0aGUgYXBwIGxvYWRlZC4gSWYgd2Ugd2VyZSBnb2luZyB0byB1c2UgdGhlIHN0YWNrZWQgbW9kZWwsIHdoaWNoIGxpYnJhcmllcyBkbyB5b3UgdGhpbmsgd2XigJlkIG5lZWQgdG8gbG9hZCBpbiBvdXIgYXBwPwoKV2UgaGF2ZSB0byBsb2FkIGFsbCB0aGUgbGlicmFyaWVzIHRoYXQgd2Ugd291bGQgbmVlZCBpbiBvcmRlciB0byBidWlsZCBvdXIgbW9kZWxzIHN1Y2ggYXMgbGFzc28sIHJhbmRvbV9mb3Jlc3QsIG9yIHhnYm9vc3QuIFRoZW4sIHdlIHdvdWxkIG5lZWQgdG8gbG9hZCBgbGlicmFyeShzdGFja3MpYC4gRmluYWxseSwgaWYgd2Ugd2FudCB0byB2aXN1YWxpemUgb3VyIG1vZGVscywgd2UgbmVlZCB0byBsb2FkIGBnZ3Bsb3QyYCwgYHRpZHl2ZXJzZWAsIGFuZCBgdGlkeW1vZGVsc2AuIAoKMy4gWW914oCZbGwgd2FudCB0aGUgdXNlciB0byBiZSBhYmxlIHRvIGNob29zZSB2YWx1ZXMgZm9yIGVhY2ggdmFyaWFibGUgaW4gdGhlIG1vZGVsLiBIb3cgd2lsbCB5b3UgY29tZSB1cCB3aXRoIHRoZSB2YWx1ZXMgdGhleSBjYW4gY2hvb3NlIGZvciBxdWFudGl0YXRpdmUgYW5kIGNhdGVnb3JpY2FsIGRhdGE/IEdpdmUgb25lIGV4YW1wbGUgZm9yIGVhY2gsIGVpdGhlciB1c2luZyBjb2RlIG9yIGluIHdvcmRzLgoKRm9yIHF1YW50aXRhdGl2ZSBkYXRhLCBJIHdvdWxkIGxldCB0aGVtIGNob29zZSB0aGUgaW5wdXQgd2l0aGluIHRoZSA1MCUgZGF0YSBpbiB0aGUgbWlkZGxlIHVzaW5nIHNsaWRlcklucHV0KCkuIEFuZCBmb3IgdGhlIGNhdGVnb3JpY2FsIGRhdGEsIEkgd291bGQgZ2l2ZSB0aGVtIHNvbWUgb3B0aW9ucyBieSB1c2luZyBzZWxlY3RJbnB1dCgpLiAKCjQuIFlvdSB3aWxsIG5lZWQgdG8gcG9wdWxhdGUgZWFjaCB2YXJpYWJsZSB3aXRoIGFuIGluaXRpYWwgdmFsdWUuIFdoaWNoIHZhbHVlIHdpbGwgeW91IGNob29zZT8gSXMgdGhlcmUgYSBuaWNlIHdheSB0byBkbyB0aGlzIHByb2dyYW1hdGljYWxseSAoaWUuIHdpdGggY29kZSk/CgpGb3IgdGhlIGluaXRpYWwgdmFsdWUsIHdlIHdvdWxkIGNob29zZSB0aGUgbWVhbiBvZiB0aGUgcXVhbnRpdGF0aXZlIHZhcmlibGVzLCBhbmQgZm9yIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIEkgd291bGQgY2hvb3NlIHRoZSBtb3N0IGRvbWluYW50IG9uZSBpbiB0aGUgZGF0YXNldC4gCgojIyBDb2RlZCBCaWFzCgpXZSB3aWxsIGJlIHdhdGNoaW5nIHNvbWUgb2YgdGhlIENvZGVkIEJpYXMgZmlsbSB0b2dldGhlciBvbiBUaHVyc2RheS4gSXQgaXMgc3RyZWFtaW5nIG9uIE5ldGZsaXguIFdyaXRlIGEgc2hvcnQgcmVmbGVjdGlvbi4gSWYgeW91IHdhbnQgc29tZSBwcm9tcHRzLCByZWZsZWN0IG9uOiBXaGF0IHBhcnQgb2YgdGhlIGZpbG0gaW1wYWN0ZWQgeW91IHRoZSBtb3N0PyBXYXMgdGhlcmUgYSBwYXJ0IHRoYXQgc3VycHJpc2VkIHlvdSBhbmQgd2h5PyBXaGF0IGVtb3Rpb25zIGRpZCB5b3UgZXhwZXJpZW5jZSB3aGlsZSB3YXRjaGluZz8KClRoZSBwYXJ0IHdoZXJlIGltcGFjdGVkIG1lIHRoZSBtb3N0IGFuZCBJIHN0aWxsIHJlbWVtYmVyIG5vdyBpcyB3aGVuIHRoZSBmYWNpYWwgcmVjb2duaXRpb24gYWxnb3JpdGhtIGNhbiBvbmx5IGRldGVjdCBoZXIgZmFjZSB3aGVuIHNoZSAgY292ZXJzIGhlciBmYWNlIHdpdGggd2hpdGUgbWFzay4gVGhlIHBhcnQgdGhhdCBzdXJwcmlzZWQgbWUgaXMgdGhhdCB0aGUgYWxnb3JpdGhtIG1ha2VzIGRlY2lzaW9uIGJhc2VkIG9uIHRoZSBkYXRhIHRoYXQgaGFzIGJlZW4gdXNlZCB0byB0cmFpbiBpdC4gSWYgdGhlIGRhdGEgaXMgYmlhc2VkLCB0aGUgYWxnb3JpdGhtIGNvdWxkIGFsc28gYmUgYmlhcy4gV2hpbGUgd2F0Y2hpbmcgaXQsIEkgZmVlbCB0aGF0IHRoaXMgaXMgYSByZWFsIGlzc3VlIGFuZCBpdCBkZXNlcnZlcyBtb3JlIGF0dGVudGlvbiBiZWluZyBwdXQgb24gaXQuIAoKCgoKCgoKCgoK